Database Connection

For the purpose of this mini-project I decided to utilize an Azure database rather than include data files, my final project next semester will heavily rely upon connected Azure databases in order to remain updated so this project gave me a good opportunity to practice with this.

The code below creates a Database Connection object which we will be querying throughout this project. You will notice the password here is visible, I am not sure if there is a way around this but for our purposes it doesn’t matter as the user login below is for a read only user so no harm will come of my database if someone decides to connect for their own purposes.

# Azure Database Connection
con <- DBI::dbConnect(odbc::odbc(), 
                      Driver = "SQL Server", 
                      Server = "ncua-dubois.database.windows.net", 
                      Database = "ncua", 
                      uid="readonlylogin",
                      pwd="DBpassword!")

Test Query

As a quick test to ensure the database connection is working the query below will select all credit unions in Florida, along with their most recent asset size, and store the result in the “testQuery” variable.

select icu.CU_NAME as 'CreditUnion', fs.ACCT_010 as 'Assets'
from tbl_Foicu icu
inner join tbl_Fs220 fs on (icu.CU_NUMBER = fs.CU_NUMBER and icu.CYCLE_DATE = fs.CYCLE_DATE)
where 1=1
and icu.CYCLE_DATE = (select max(CYCLE_DATE) from tbl_Foicu)
and icu.STATE='FL'
order by icu.CU_NAME asc

We can see from the head() command below that the query did work successfully.

head(testQuery)

Branch Locations

For the Spatial Analysis portion of this project, the most logical topic to look at is where branches are located, the query below pulls in the locations of all Credit Union branches in the continental US as of the end of every quarter from December 2019 to June 2021. The purpose of this was to look at Covid’s effect on Credit Union Branches, which we will look into deeper later.

select cast(CYCLE_DATE as date) as Date, CU_NAME, Lat as y, Long as x, PhysicalAddressStateCode as State
from tbl_BranchLocations
where 1=1
and PhysicalAddressStateCode not in ('PR','VI','GU','HI','AK')

We can see below that the Branches have already been Geocoded, this was a step I completed outside of R as there are 23,161 unique addresses in this database which would far overwhelm R’s geocoding abilities.

head(cu_branches)

Now that we have the data we need to format it to be plot-able on a map, this is done using the code below.

state_map_data <- map('state', fill = TRUE, plot = FALSE) %>% st_as_sf()

cu_branches_21_sf <- st_as_sf(filter(cu_branches,Date=='2021-06-30'), coords = c('x','y'))
cu_branches_21_sf <- cu_branches_21_sf %>%
  st_set_crs(st_crs(state_map_data))

The ggplot below shows the locations of every Credit Union Branch in relation to the United States.

ggplot() +
  geom_sf(data = state_map_data)+
  geom_sf(data = cu_branches_21_sf, color = 'blue', size = 0.1)+
  labs(title="Credit Union Branches", subtitle="Continental US Only", caption="Source: NCUA (June 2021)")+
  theme(plot.title.position = "plot")

Branch Locations by State

After seeing the map above my initial objective was to get a total count by state, originally I was going to achieve this by doing a simple group_by, and this is why we can see the query includes a column for the branch state. But I changed my mind on this and decided to have R calculate this using the st_intersects function.

The function below calculates the number of points inside each state in the previous map and stores the result in the branch_count attribute in the state sf object.

sf::sf_use_s2(FALSE)
state_map_data$branch_count <- lengths(st_intersects(state_map_data,cu_branches_21_sf))

The plot below using that branch_count attribute to color each state. I originally has plotted the branches on top of this map, similar to the previous map but it felt redundant and cluttered the map. We can see that Texas and California have the most Credit Union Branches which makes sense based on their populations. Perhaps in a future project I will normalize these results by population.

ggplot() +
  geom_sf(data = state_map_data, aes(fill = branch_count))+
  scale_fill_distiller(palette="Greens", direction = +1)+
  #geom_sf(data = cu_branches_21_sf, color = 'black', size = 0.00005)+
  labs(title="Number of Credit Union Branches by State", subtitle="Continental US Only", caption="Source: NCUA (June 2021)",fill='Credit Union Branches')+
  theme(plot.title.position = "plot",legend.position = "bottom")

NA

COVID’s Effect on Branches

As a quick side question I wanted to see how many branches existed prior to COVID compared to now so I graphed the results as a line graph over time below. We can see there was a small dip in the first quarter of 2020 but the number of branches has remained relatively stable. Keep in mind to show the variance I had to play with the Y axis a bit. Less than 2% of branches closed in the first quarter of 2020.

date_branch_counts <- cu_branches %>%
  group_by(Date) %>%
  summarise(count_branches = n())
ggplot(date_branch_counts, aes(x=Date, y=count_branches, group=1))+
  geom_point()+
  geom_line()+
  ylim(20000,22000)+
  theme_minimal()+
  labs(title="Number of CU Branchs", subtitle="Continental US Only", caption="Source: NCUA", y="Count of Branches",x="")+
  theme(plot.title.position = "plot")

Branch Changes in Florida

As a final mapping analysis question for this project I wanted to see the change in Credit Union Branch locations in Florida between December 31st, 2019 and June 30th, 2021. The query below pulls the differences between these two dates and labels if the branch closed, opened or remained existing throughout this time period.

select cast(c.CYCLE_DATE as date) as Date, c.CU_NAME, c.Lat as y, c.Long as x, c.PhysicalAddressStateCode as State,
case when p.SiteId is null then 'New' else 'Existing' end as 'Status'
from tbl_BranchLocations c
left outer join tbl_BranchLocations p on (c.CU_NUMBER = p.CU_NUMBER and c.SiteId = p.SiteId and p.CYCLE_DATE='2019-12-31')
where 1=1
and c.PhysicalAddressStateCode in ('FL')
and c.CYCLE_DATE in ('2021-06-30')

Union

select cast(c.CYCLE_DATE as date) as Date, c.CU_NAME, c.Lat as y, c.Long as x, c.PhysicalAddressStateCode as State,
case when p.SiteId is null then 'Closed' else 'Existing' end as 'Status'
from tbl_BranchLocations c
left outer join tbl_BranchLocations p on (c.CU_NUMBER = p.CU_NUMBER and c.SiteId = p.SiteId and p.CYCLE_DATE='2021-06-30')
where 1=1
and c.PhysicalAddressStateCode in ('FL')
and c.CYCLE_DATE in ('2019-12-31')

order by 2,3,4,1

The code below generates two maps, one for which branches closed and one for which branches opened. I decided to map these seperately do they do not overlap and hide any information.

new_map <- ggplot()+
  geom_sf(data = filter(state_map_data,ID=="florida")) +
  geom_point(data = filter(florida_br_change,Date=='2021-06-30',State=="FL",Status=="Existing"), aes(x = x, y = y), color="Dark Grey", size = 0.1)+
  geom_point(data = filter(florida_br_change,Date=='2021-06-30',State=="FL",Status=="New"), aes(x = x, y = y), color="Dark Green", size = 2)+
  theme_minimal()+
  theme(legend.position="bottom", legend.key.width = unit(1, 'cm'))+
  labs(title=" ", subtitle="Opened Between December 2019 and June 2021",y="",x="",caption="Source: NCUA")+
  theme(plot.title.position = "plot")+
  theme(axis.text.y = element_text(color = "white"))

old_map <- ggplot()+
  geom_sf(data = filter(state_map_data,ID=="florida")) +
  geom_point(data = filter(florida_br_change,Date=='2019-12-31',State=="FL",Status=="Existing"), aes(x = x, y = y), color="Dark Grey", size = 0.1)+
  geom_point(data = filter(florida_br_change,Date=='2019-12-31',State=="FL",Status=="Closed"), aes(x = x, y = y), color="Dark Red", size = 2)+
  theme_minimal()+
  theme(legend.position="bottom", legend.key.width = unit(1, 'cm'))+
  labs(title="Recent Credit Union Branch Openings and Closings", subtitle="Closed Between December 2019 and June 2021",y="",x="",caption=" ")+
  theme(plot.title.position = "plot")
  

The plot below shows the two maps, with the left map showing all credit union branches which closed between these dates and the right map showing all credit union branches which opened. I found these results very interesting, although it doesn’t paint a clear picture, branches are both closing and opening at roughly the same rate. I did notice though, that the Southwest Florida market (the Sarasota, Bradenton, and Port Charlotte areas) has seen 4 openings and no closings. I have known about the potential in this area for a while, so to see this being taken advantage of is encouraging.

grid.arrange(old_map, new_map, ncol=2)

Interactive Plot

For the Interactive plot portion of this Mini-Project I decided to look into the Asset Trends of the top 10 Credit Unions in Florida. I know from past projects that the top 10 Credit Unions are Addition Financial, Campus USA, Eglin, Fairwinds, Grow Financial, GTE, Midflorida, Space Coast, Suncoast, and Vystar.

select icu.CU_NAME as 'CreditUnion'
, cast(fs.ACCT_010 as money) as 'Assets'
, (fs.ACCT_010-st.ACCT_010+0.0)/st.ACCT_010 as 'AssetGrowth'
, cast(fs.CYCLE_DATE as datetime) as 'Cycle_Date'
from tbl_Foicu icu
inner join tbl_Fs220 fs on (icu.CU_NUMBER = fs.CU_NUMBER and icu.CYCLE_DATE = fs.CYCLE_DATE)
left outer join tbl_Fs220 st on (icu.CU_NUMBER = st.CU_NUMBER and st.CYCLE_DATE='2019-12-31')
where 1=1
and icu.STATE='FL'
and icu.cu_number in (68645,68490,67297,68600,68417,9976,196,9788,68391,68702)
order by icu.CU_NAME asc, fs.CYCLE_DATE desc
top_ten_assets$Cycle_Date <- as.Date(top_ten_assets$Cycle_Date)
asset_trend <- ggplot(data = top_ten_assets)+
  geom_line(aes(x = Cycle_Date, y = Assets/1000000000, color=CreditUnion), size=1)+
  geom_line(aes(x = Cycle_Date, y = Assets/1000000000, color=CreditUnion, text=paste("Credit Union: ",CreditUnion,"<br>Date:",Cycle_Date,"<br>Assets: $", format(Assets,big.mark=","))), size=1)+
  scale_y_continuous(labels=scales::dollar_format(),breaks = round(seq(0, 15, by = 1),1))+
  scale_x_date(date_breaks = "2 month", date_labels =  "%b %y")+
  labs(y="Assets ($ Billions)",x="Cycle Date",caption=" ", color="Credit Union",title=" ")+
  theme(plot.title.position = "plot")
Ignoring unknown aesthetics: text
asset_plotly <- ggplotly(asset_trend, tooltip = "text", width = 1000) %>%
  layout(title = list(text = paste0('Asset Trend',
                                    '<br>',
                                    '<sup>',
                                    'Top 10 Florida Credit Unions',
                                    '</sup>')))  
asset_plotly
growth_trend <- ggplot(data = top_ten_assets)+
  geom_line(aes(x = Cycle_Date, y = AssetGrowth, color=CreditUnion), size=1)+
  geom_line(aes(x = Cycle_Date, y = AssetGrowth, color=CreditUnion, text=paste("Credit Union: ",CreditUnion,"<br>Date:",Cycle_Date,"<br>Assets: $", format(Assets,big.mark=","),"<br>Growth: ",round(AssetGrowth*100,1),"%")), size=1)+
  scale_y_continuous(labels=scales::percent_format())+
  scale_x_date(date_breaks = "2 month", date_labels =  "%b %y")+
  labs(y="Assets Growth (%)",x="Cycle Date", color="Credit Union",title=" ")
Ignoring unknown aesthetics: text
  #theme(plot.title.position = "plot")
growth_plotly <- ggplotly(growth_trend, tooltip = "text", width = 1000) %>%
  layout(title = list(text = paste0('Asset Growth Since 2019',
                                    '<br>',
                                    '<sup>',
                                    'Top 10 Florida Credit Unions',
                                    '</sup>')))
growth_plotly
LS0tDQp0aXRsZTogIk1pbmktUHJvamVjdCAyIg0KYXV0aG9yOiAiSmVmZnJleSBEdUJvaXMiDQpkYXRlOiAiMTAvMjYvMjAyMSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoZ2dtYXApDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShEQkkpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShkYnBseXIpDQpsaWJyYXJ5KG9kYmMpDQpsaWJyYXJ5KG1hcHMpDQpsaWJyYXJ5KGdncmVwZWwpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmBgYA0KIA0KIyBEYXRhYmFzZSBDb25uZWN0aW9uDQoNCkZvciB0aGUgcHVycG9zZSBvZiB0aGlzIG1pbmktcHJvamVjdCBJIGRlY2lkZWQgdG8gdXRpbGl6ZSBhbiBBenVyZSBkYXRhYmFzZSByYXRoZXIgdGhhbiBpbmNsdWRlIGRhdGEgZmlsZXMsIG15IGZpbmFsIHByb2plY3QgbmV4dCBzZW1lc3RlciB3aWxsIGhlYXZpbHkgcmVseSB1cG9uIGNvbm5lY3RlZCBBenVyZSBkYXRhYmFzZXMgaW4gb3JkZXIgdG8gcmVtYWluIHVwZGF0ZWQgc28gdGhpcyBwcm9qZWN0IGdhdmUgbWUgYSBnb29kIG9wcG9ydHVuaXR5IHRvIHByYWN0aWNlIHdpdGggdGhpcy4NCg0KVGhlIGNvZGUgYmVsb3cgY3JlYXRlcyBhIERhdGFiYXNlIENvbm5lY3Rpb24gb2JqZWN0IHdoaWNoIHdlIHdpbGwgYmUgcXVlcnlpbmcgdGhyb3VnaG91dCB0aGlzIHByb2plY3QuIFlvdSB3aWxsIG5vdGljZSB0aGUgcGFzc3dvcmQgaGVyZSBpcyB2aXNpYmxlLCBJIGFtIG5vdCBzdXJlIGlmIHRoZXJlIGlzIGEgd2F5IGFyb3VuZCB0aGlzIGJ1dCBmb3Igb3VyIHB1cnBvc2VzIGl0IGRvZXNuJ3QgbWF0dGVyIGFzIHRoZSB1c2VyIGxvZ2luIGJlbG93IGlzIGZvciBhIHJlYWQgb25seSB1c2VyIHNvIG5vIGhhcm0gd2lsbCBjb21lIG9mIG15IGRhdGFiYXNlIGlmIHNvbWVvbmUgZGVjaWRlcyB0byBjb25uZWN0IGZvciB0aGVpciBvd24gcHVycG9zZXMuDQpgYGB7cn0NCiMgQXp1cmUgRGF0YWJhc2UgQ29ubmVjdGlvbg0KY29uIDwtIERCSTo6ZGJDb25uZWN0KG9kYmM6Om9kYmMoKSwgDQogICAgICAgICAgICAgICAgICAgICAgRHJpdmVyID0gIlNRTCBTZXJ2ZXIiLCANCiAgICAgICAgICAgICAgICAgICAgICBTZXJ2ZXIgPSAibmN1YS1kdWJvaXMuZGF0YWJhc2Uud2luZG93cy5uZXQiLCANCiAgICAgICAgICAgICAgICAgICAgICBEYXRhYmFzZSA9ICJuY3VhIiwgDQogICAgICAgICAgICAgICAgICAgICAgdWlkPSJyZWFkb25seWxvZ2luIiwNCiAgICAgICAgICAgICAgICAgICAgICBwd2Q9IkRCcGFzc3dvcmQhIikNCmBgYA0KDQojIyMgVGVzdCBRdWVyeQ0KDQpBcyBhIHF1aWNrIHRlc3QgdG8gZW5zdXJlIHRoZSBkYXRhYmFzZSBjb25uZWN0aW9uIGlzIHdvcmtpbmcgdGhlIHF1ZXJ5IGJlbG93IHdpbGwgc2VsZWN0IGFsbCBjcmVkaXQgdW5pb25zIGluIEZsb3JpZGEsIGFsb25nIHdpdGggdGhlaXIgbW9zdCByZWNlbnQgYXNzZXQgc2l6ZSwgYW5kIHN0b3JlIHRoZSByZXN1bHQgaW4gdGhlICJ0ZXN0UXVlcnkiIHZhcmlhYmxlLg0KYGBge3NxbCwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXIgPSAidGVzdFF1ZXJ5In0NCg0Kc2VsZWN0IGljdS5DVV9OQU1FIGFzICdDcmVkaXRVbmlvbicsIGZzLkFDQ1RfMDEwIGFzICdBc3NldHMnDQpmcm9tIHRibF9Gb2ljdSBpY3UNCmlubmVyIGpvaW4gdGJsX0ZzMjIwIGZzIG9uIChpY3UuQ1VfTlVNQkVSID0gZnMuQ1VfTlVNQkVSIGFuZCBpY3UuQ1lDTEVfREFURSA9IGZzLkNZQ0xFX0RBVEUpDQp3aGVyZSAxPTENCmFuZCBpY3UuQ1lDTEVfREFURSA9IChzZWxlY3QgbWF4KENZQ0xFX0RBVEUpIGZyb20gdGJsX0ZvaWN1KQ0KYW5kIGljdS5TVEFURT0nRkwnDQpvcmRlciBieSBpY3UuQ1VfTkFNRSBhc2MNCmBgYA0KDQpXZSBjYW4gc2VlIGZyb20gdGhlIGhlYWQoKSBjb21tYW5kIGJlbG93IHRoYXQgdGhlIHF1ZXJ5IGRpZCB3b3JrIHN1Y2Nlc3NmdWxseS4NCmBgYHtyfQ0KaGVhZCh0ZXN0UXVlcnkpDQpgYGANCg0KIyBCcmFuY2ggTG9jYXRpb25zDQoNCkZvciB0aGUgU3BhdGlhbCBBbmFseXNpcyBwb3J0aW9uIG9mIHRoaXMgcHJvamVjdCwgdGhlIG1vc3QgbG9naWNhbCB0b3BpYyB0byBsb29rIGF0IGlzIHdoZXJlIGJyYW5jaGVzIGFyZSBsb2NhdGVkLCB0aGUgcXVlcnkgYmVsb3cgcHVsbHMgaW4gdGhlIGxvY2F0aW9ucyBvZiBhbGwgQ3JlZGl0IFVuaW9uIGJyYW5jaGVzIGluIHRoZSBjb250aW5lbnRhbCBVUyBhcyBvZiB0aGUgZW5kIG9mIGV2ZXJ5IHF1YXJ0ZXIgZnJvbSBEZWNlbWJlciAyMDE5IHRvIEp1bmUgMjAyMS4gVGhlIHB1cnBvc2Ugb2YgdGhpcyB3YXMgdG8gbG9vayBhdCBDb3ZpZCdzIGVmZmVjdCBvbiBDcmVkaXQgVW5pb24gQnJhbmNoZXMsIHdoaWNoIHdlIHdpbGwgbG9vayBpbnRvIGRlZXBlciBsYXRlci4NCmBgYHtzcWwsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyID0gImN1X2JyYW5jaGVzIn0NCnNlbGVjdCBjYXN0KENZQ0xFX0RBVEUgYXMgZGF0ZSkgYXMgRGF0ZSwgQ1VfTkFNRSwgTGF0IGFzIHksIExvbmcgYXMgeCwgUGh5c2ljYWxBZGRyZXNzU3RhdGVDb2RlIGFzIFN0YXRlDQpmcm9tIHRibF9CcmFuY2hMb2NhdGlvbnMNCndoZXJlIDE9MQ0KYW5kIFBoeXNpY2FsQWRkcmVzc1N0YXRlQ29kZSBub3QgaW4gKCdQUicsJ1ZJJywnR1UnLCdISScsJ0FLJykNCmBgYA0KDQpXZSBjYW4gc2VlIGJlbG93IHRoYXQgdGhlIEJyYW5jaGVzIGhhdmUgYWxyZWFkeSBiZWVuIEdlb2NvZGVkLCB0aGlzIHdhcyBhIHN0ZXAgSSBjb21wbGV0ZWQgb3V0c2lkZSBvZiBSIGFzIHRoZXJlIGFyZSAyMywxNjEgdW5pcXVlIGFkZHJlc3NlcyBpbiB0aGlzIGRhdGFiYXNlIHdoaWNoIHdvdWxkIGZhciBvdmVyd2hlbG0gUidzIGdlb2NvZGluZyBhYmlsaXRpZXMuDQpgYGB7cn0NCmhlYWQoY3VfYnJhbmNoZXMpDQpgYGANCg0KTm93IHRoYXQgd2UgaGF2ZSB0aGUgZGF0YSB3ZSBuZWVkIHRvIGZvcm1hdCBpdCB0byBiZSBwbG90LWFibGUgb24gYSBtYXAsIHRoaXMgaXMgZG9uZSB1c2luZyB0aGUgY29kZSBiZWxvdy4NCmBgYHtyfQ0Kc3RhdGVfbWFwX2RhdGEgPC0gbWFwKCdzdGF0ZScsIGZpbGwgPSBUUlVFLCBwbG90ID0gRkFMU0UpICU+JSBzdF9hc19zZigpICNHZW5lcmF0ZXMgYSBtYXAgb2YgdGhlIENvbnRpbmVudGFsIFVuaXRlZCBTdGF0ZXMNCg0KY3VfYnJhbmNoZXNfMjFfc2YgPC0gc3RfYXNfc2YoZmlsdGVyKGN1X2JyYW5jaGVzLERhdGU9PScyMDIxLTA2LTMwJyksIGNvb3JkcyA9IGMoJ3gnLCd5JykpICNDcmVhdGVzIFNGIG9iamVjdCBmcm9tIG91ciBicmFuY2ggbG9jYXRpb24gcXVlcnkgcmVzdWx0cyBmaWx0ZXJpbmcgdG8gb25seSBpbmNsdWRlIHRoZSBtb3N0IHJlY2VudCByZXN1bHRzDQpjdV9icmFuY2hlc18yMV9zZiA8LSBzdF9zZXRfY3JzKGN1X2JyYW5jaGVzXzIxX3NmLCBzdF9jcnMoc3RhdGVfbWFwX2RhdGEpKSAjQ29udmVydHMgb3VyIGJyYW5jaCBTRiBvYmplY3QgdG8gdXNlIHRoZSBzYW1lIGNvb3JpZG5hdGVzIHJlZmVyZW5jZSBzeXN0ZW0gYXMgdGhlIFVTIE1hcA0KDQpgYGANCg0KVGhlIGdncGxvdCBiZWxvdyBzaG93cyB0aGUgbG9jYXRpb25zIG9mIGV2ZXJ5IENyZWRpdCBVbmlvbiBCcmFuY2ggaW4gcmVsYXRpb24gdG8gdGhlIFVuaXRlZCBTdGF0ZXMuDQpgYGB7cn0NCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gc3RhdGVfbWFwX2RhdGEpKw0KICBnZW9tX3NmKGRhdGEgPSBjdV9icmFuY2hlc18yMV9zZiwgY29sb3IgPSAnYmx1ZScsIHNpemUgPSAwLjEpKw0KICBsYWJzKHRpdGxlPSJDcmVkaXQgVW5pb24gQnJhbmNoZXMiLCBzdWJ0aXRsZT0iQ29udGluZW50YWwgVVMgT25seSIsIGNhcHRpb249IlNvdXJjZTogTkNVQSAoSnVuZSAyMDIxKSIpKw0KICB0aGVtZShwbG90LnRpdGxlLnBvc2l0aW9uID0gInBsb3QiKQ0KYGBgDQoNCiMjIyBCcmFuY2ggTG9jYXRpb25zIGJ5IFN0YXRlDQoNCkFmdGVyIHNlZWluZyB0aGUgbWFwIGFib3ZlIG15IGluaXRpYWwgb2JqZWN0aXZlIHdhcyB0byBnZXQgYSB0b3RhbCBjb3VudCBieSBzdGF0ZSwgb3JpZ2luYWxseSBJIHdhcyBnb2luZyB0byBhY2hpZXZlIHRoaXMgYnkgZG9pbmcgYSBzaW1wbGUgZ3JvdXBfYnksIGFuZCB0aGlzIGlzIHdoeSB3ZSBjYW4gc2VlIHRoZSBxdWVyeSBpbmNsdWRlcyBhIGNvbHVtbiBmb3IgdGhlIGJyYW5jaCBzdGF0ZS4gQnV0IEkgY2hhbmdlZCBteSBtaW5kIG9uIHRoaXMgYW5kIGRlY2lkZWQgdG8gaGF2ZSBSIGNhbGN1bGF0ZSB0aGlzIHVzaW5nIHRoZSBzdF9pbnRlcnNlY3RzIGZ1bmN0aW9uLg0KDQpUaGUgZnVuY3Rpb24gYmVsb3cgY2FsY3VsYXRlcyB0aGUgbnVtYmVyIG9mIHBvaW50cyBpbnNpZGUgZWFjaCBzdGF0ZSBpbiB0aGUgcHJldmlvdXMgbWFwIGFuZCBzdG9yZXMgdGhlIHJlc3VsdCBpbiB0aGUgYnJhbmNoX2NvdW50IGF0dHJpYnV0ZSBpbiB0aGUgc3RhdGUgc2Ygb2JqZWN0Lg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpzZjo6c2ZfdXNlX3MyKEZBTFNFKQ0Kc3RhdGVfbWFwX2RhdGEkYnJhbmNoX2NvdW50IDwtIGxlbmd0aHMoc3RfaW50ZXJzZWN0cyhzdGF0ZV9tYXBfZGF0YSxjdV9icmFuY2hlc18yMV9zZikpDQpgYGANCg0KVGhlIHBsb3QgYmVsb3cgdXNpbmcgdGhhdCBicmFuY2hfY291bnQgYXR0cmlidXRlIHRvIGNvbG9yIGVhY2ggc3RhdGUuIEkgb3JpZ2luYWxseSBoYXMgcGxvdHRlZCB0aGUgYnJhbmNoZXMgb24gdG9wIG9mIHRoaXMgbWFwLCBzaW1pbGFyIHRvIHRoZSBwcmV2aW91cyBtYXAgYnV0IGl0IGZlbHQgcmVkdW5kYW50IGFuZCBjbHV0dGVyZWQgdGhlIG1hcC4NCldlIGNhbiBzZWUgdGhhdCBUZXhhcyBhbmQgQ2FsaWZvcm5pYSBoYXZlIHRoZSBtb3N0IENyZWRpdCBVbmlvbiBCcmFuY2hlcyB3aGljaCBtYWtlcyBzZW5zZSBiYXNlZCBvbiB0aGVpciBwb3B1bGF0aW9ucy4gUGVyaGFwcyBpbiBhIGZ1dHVyZSBwcm9qZWN0IEkgd2lsbCBub3JtYWxpemUgdGhlc2UgcmVzdWx0cyBieSBwb3B1bGF0aW9uLg0KYGBge3J9DQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHN0YXRlX21hcF9kYXRhLCBhZXMoZmlsbCA9IGJyYW5jaF9jb3VudCkpKw0KICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlPSJHcmVlbnMiLCBkaXJlY3Rpb24gPSArMSkrDQogICNnZW9tX3NmKGRhdGEgPSBjdV9icmFuY2hlc18yMV9zZiwgY29sb3IgPSAnYmxhY2snLCBzaXplID0gMC4wMDAwNSkrDQogIGxhYnModGl0bGU9Ik51bWJlciBvZiBDcmVkaXQgVW5pb24gQnJhbmNoZXMgYnkgU3RhdGUiLCBzdWJ0aXRsZT0iQ29udGluZW50YWwgVVMgT25seSIsIGNhcHRpb249IlNvdXJjZTogTkNVQSAoSnVuZSAyMDIxKSIsZmlsbD0nQ3JlZGl0IFVuaW9uIEJyYW5jaGVzJykrDQogIHRoZW1lKHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIsbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQogIA0KYGBgDQoNCiMjIyBDT1ZJRCdzIEVmZmVjdCBvbiBCcmFuY2hlcw0KDQpBcyBhIHF1aWNrIHNpZGUgcXVlc3Rpb24gSSB3YW50ZWQgdG8gc2VlIGhvdyBtYW55IGJyYW5jaGVzIGV4aXN0ZWQgcHJpb3IgdG8gQ09WSUQgY29tcGFyZWQgdG8gbm93IHNvIEkgZ3JhcGhlZCB0aGUgcmVzdWx0cyBhcyBhIGxpbmUgZ3JhcGggb3ZlciB0aW1lIGJlbG93LiBXZSBjYW4gc2VlIHRoZXJlIHdhcyBhIHNtYWxsIGRpcCBpbiB0aGUgZmlyc3QgcXVhcnRlciBvZiAyMDIwIGJ1dCB0aGUgbnVtYmVyIG9mIGJyYW5jaGVzIGhhcyByZW1haW5lZCByZWxhdGl2ZWx5IHN0YWJsZS4gS2VlcCBpbiBtaW5kIHRvIHNob3cgdGhlIHZhcmlhbmNlIEkgaGFkIHRvIHBsYXkgd2l0aCB0aGUgWSBheGlzIGEgYml0LiBMZXNzIHRoYW4gMiUgb2YgYnJhbmNoZXMgY2xvc2VkIGluIHRoZSBmaXJzdCBxdWFydGVyIG9mIDIwMjAuDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCmRhdGVfYnJhbmNoX2NvdW50cyA8LSBjdV9icmFuY2hlcyAlPiUNCiAgZ3JvdXBfYnkoRGF0ZSkgJT4lDQogIHN1bW1hcmlzZShjb3VudF9icmFuY2hlcyA9IG4oKSkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYXRlX2JyYW5jaF9jb3VudHMsIGFlcyh4PURhdGUsIHk9Y291bnRfYnJhbmNoZXMsIGdyb3VwPTEpKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX2xpbmUoKSsNCiAgeWxpbSgyMDAwMCwyMjAwMCkrDQogIHRoZW1lX21pbmltYWwoKSsNCiAgbGFicyh0aXRsZT0iTnVtYmVyIG9mIENVIEJyYW5jaHMiLCBzdWJ0aXRsZT0iQ29udGluZW50YWwgVVMgT25seSIsIGNhcHRpb249IlNvdXJjZTogTkNVQSIsIHk9IkNvdW50IG9mIEJyYW5jaGVzIix4PSIiKSsNCiAgdGhlbWUocGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IikNCmBgYA0KDQojIyMgQnJhbmNoIENoYW5nZXMgaW4gRmxvcmlkYQ0KDQpBcyBhIGZpbmFsIG1hcHBpbmcgYW5hbHlzaXMgcXVlc3Rpb24gZm9yIHRoaXMgcHJvamVjdCBJIHdhbnRlZCB0byBzZWUgdGhlIGNoYW5nZSBpbiBDcmVkaXQgVW5pb24gQnJhbmNoIGxvY2F0aW9ucyBpbiBGbG9yaWRhIGJldHdlZW4gRGVjZW1iZXIgMzFzdCwgMjAxOSBhbmQgSnVuZSAzMHRoLCAyMDIxLiBUaGUgcXVlcnkgYmVsb3cgcHVsbHMgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlc2UgdHdvIGRhdGVzIGFuZCBsYWJlbHMgaWYgdGhlIGJyYW5jaCBjbG9zZWQsIG9wZW5lZCBvciByZW1haW5lZCBleGlzdGluZyB0aHJvdWdob3V0IHRoaXMgdGltZSBwZXJpb2QuDQpgYGB7c3FsLCBjb25uZWN0aW9uPWNvbiwgb3V0cHV0LnZhciA9ICJmbG9yaWRhX2JyX2NoYW5nZSJ9DQpzZWxlY3QgY2FzdChjLkNZQ0xFX0RBVEUgYXMgZGF0ZSkgYXMgRGF0ZSwgYy5DVV9OQU1FLCBjLkxhdCBhcyB5LCBjLkxvbmcgYXMgeCwgYy5QaHlzaWNhbEFkZHJlc3NTdGF0ZUNvZGUgYXMgU3RhdGUsDQpjYXNlIHdoZW4gcC5TaXRlSWQgaXMgbnVsbCB0aGVuICdOZXcnIGVsc2UgJ0V4aXN0aW5nJyBlbmQgYXMgJ1N0YXR1cycNCmZyb20gdGJsX0JyYW5jaExvY2F0aW9ucyBjDQpsZWZ0IG91dGVyIGpvaW4gdGJsX0JyYW5jaExvY2F0aW9ucyBwIG9uIChjLkNVX05VTUJFUiA9IHAuQ1VfTlVNQkVSIGFuZCBjLlNpdGVJZCA9IHAuU2l0ZUlkIGFuZCBwLkNZQ0xFX0RBVEU9JzIwMTktMTItMzEnKQ0Kd2hlcmUgMT0xDQphbmQgYy5QaHlzaWNhbEFkZHJlc3NTdGF0ZUNvZGUgaW4gKCdGTCcpDQphbmQgYy5DWUNMRV9EQVRFIGluICgnMjAyMS0wNi0zMCcpDQoNClVuaW9uDQoNCnNlbGVjdCBjYXN0KGMuQ1lDTEVfREFURSBhcyBkYXRlKSBhcyBEYXRlLCBjLkNVX05BTUUsIGMuTGF0IGFzIHksIGMuTG9uZyBhcyB4LCBjLlBoeXNpY2FsQWRkcmVzc1N0YXRlQ29kZSBhcyBTdGF0ZSwNCmNhc2Ugd2hlbiBwLlNpdGVJZCBpcyBudWxsIHRoZW4gJ0Nsb3NlZCcgZWxzZSAnRXhpc3RpbmcnIGVuZCBhcyAnU3RhdHVzJw0KZnJvbSB0YmxfQnJhbmNoTG9jYXRpb25zIGMNCmxlZnQgb3V0ZXIgam9pbiB0YmxfQnJhbmNoTG9jYXRpb25zIHAgb24gKGMuQ1VfTlVNQkVSID0gcC5DVV9OVU1CRVIgYW5kIGMuU2l0ZUlkID0gcC5TaXRlSWQgYW5kIHAuQ1lDTEVfREFURT0nMjAyMS0wNi0zMCcpDQp3aGVyZSAxPTENCmFuZCBjLlBoeXNpY2FsQWRkcmVzc1N0YXRlQ29kZSBpbiAoJ0ZMJykNCmFuZCBjLkNZQ0xFX0RBVEUgaW4gKCcyMDE5LTEyLTMxJykNCg0Kb3JkZXIgYnkgMiwzLDQsMQ0KYGBgDQoNClRoZSBjb2RlIGJlbG93IGdlbmVyYXRlcyB0d28gbWFwcywgb25lIGZvciB3aGljaCBicmFuY2hlcyBjbG9zZWQgYW5kIG9uZSBmb3Igd2hpY2ggYnJhbmNoZXMgb3BlbmVkLiBJIGRlY2lkZWQgdG8gbWFwIHRoZXNlIHNlcGVyYXRlbHkgZG8gdGhleSBkbyBub3Qgb3ZlcmxhcCBhbmQgaGlkZSBhbnkgaW5mb3JtYXRpb24uDQpgYGB7cn0NCm5ld19tYXAgPC0gZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YSA9IGZpbHRlcihzdGF0ZV9tYXBfZGF0YSxJRD09ImZsb3JpZGEiKSkgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBmaWx0ZXIoZmxvcmlkYV9icl9jaGFuZ2UsRGF0ZT09JzIwMjEtMDYtMzAnLFN0YXRlPT0iRkwiLFN0YXR1cz09IkV4aXN0aW5nIiksIGFlcyh4ID0geCwgeSA9IHkpLCBjb2xvcj0iRGFyayBHcmV5Iiwgc2l6ZSA9IDAuMSkrDQogIGdlb21fcG9pbnQoZGF0YSA9IGZpbHRlcihmbG9yaWRhX2JyX2NoYW5nZSxEYXRlPT0nMjAyMS0wNi0zMCcsU3RhdGU9PSJGTCIsU3RhdHVzPT0iTmV3IiksIGFlcyh4ID0geCwgeSA9IHkpLCBjb2xvcj0iRGFyayBHcmVlbiIsIHNpemUgPSAyKSsNCiAgdGhlbWVfbWluaW1hbCgpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDEsICdjbScpKSsNCiAgbGFicyh0aXRsZT0iICIsIHN1YnRpdGxlPSJPcGVuZWQgQmV0d2VlbiBEZWNlbWJlciAyMDE5IGFuZCBKdW5lIDIwMjEiLHk9IiIseD0iIixjYXB0aW9uPSJTb3VyY2U6IE5DVUEiKSsNCiAgdGhlbWUocGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IikrDQogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gIndoaXRlIikpDQoNCm9sZF9tYXAgPC0gZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YSA9IGZpbHRlcihzdGF0ZV9tYXBfZGF0YSxJRD09ImZsb3JpZGEiKSkgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBmaWx0ZXIoZmxvcmlkYV9icl9jaGFuZ2UsRGF0ZT09JzIwMTktMTItMzEnLFN0YXRlPT0iRkwiLFN0YXR1cz09IkV4aXN0aW5nIiksIGFlcyh4ID0geCwgeSA9IHkpLCBjb2xvcj0iRGFyayBHcmV5Iiwgc2l6ZSA9IDAuMSkrDQogIGdlb21fcG9pbnQoZGF0YSA9IGZpbHRlcihmbG9yaWRhX2JyX2NoYW5nZSxEYXRlPT0nMjAxOS0xMi0zMScsU3RhdGU9PSJGTCIsU3RhdHVzPT0iQ2xvc2VkIiksIGFlcyh4ID0geCwgeSA9IHkpLCBjb2xvcj0iRGFyayBSZWQiLCBzaXplID0gMikrDQogIHRoZW1lX21pbmltYWwoKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLCBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgxLCAnY20nKSkrDQogIGxhYnModGl0bGU9IlJlY2VudCBDcmVkaXQgVW5pb24gQnJhbmNoIE9wZW5pbmdzIGFuZCBDbG9zaW5ncyIsIHN1YnRpdGxlPSJDbG9zZWQgQmV0d2VlbiBEZWNlbWJlciAyMDE5IGFuZCBKdW5lIDIwMjEiLHk9IiIseD0iIixjYXB0aW9uPSIgIikrDQogIHRoZW1lKHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIpDQogIA0KYGBgDQoNClRoZSBwbG90IGJlbG93IHNob3dzIHRoZSB0d28gbWFwcywgd2l0aCB0aGUgbGVmdCBtYXAgc2hvd2luZyBhbGwgY3JlZGl0IHVuaW9uIGJyYW5jaGVzIHdoaWNoIGNsb3NlZCBiZXR3ZWVuIHRoZXNlIGRhdGVzIGFuZCB0aGUgcmlnaHQgbWFwIHNob3dpbmcgYWxsIGNyZWRpdCB1bmlvbiBicmFuY2hlcyB3aGljaCBvcGVuZWQuDQpJIGZvdW5kIHRoZXNlIHJlc3VsdHMgdmVyeSBpbnRlcmVzdGluZywgYWx0aG91Z2ggaXQgZG9lc24ndCBwYWludCBhIGNsZWFyIHBpY3R1cmUsIGJyYW5jaGVzIGFyZSBib3RoIGNsb3NpbmcgYW5kIG9wZW5pbmcgYXQgcm91Z2hseSB0aGUgc2FtZSByYXRlLiBJIGRpZCBub3RpY2UgdGhvdWdoLCB0aGF0IHRoZSBTb3V0aHdlc3QgRmxvcmlkYSBtYXJrZXQgKHRoZSBTYXJhc290YSwgQnJhZGVudG9uLCBhbmQgUG9ydCBDaGFybG90dGUgYXJlYXMpIGhhcyBzZWVuIDQgb3BlbmluZ3MgYW5kIG5vIGNsb3NpbmdzLiBJIGhhdmUga25vd24gYWJvdXQgdGhlIHBvdGVudGlhbCBpbiB0aGlzIGFyZWEgZm9yIGEgd2hpbGUsIHNvIHRvIHNlZSB0aGlzIGJlaW5nIHRha2VuIGFkdmFudGFnZSBvZiBpcyBlbmNvdXJhZ2luZy4NCmBgYHtyLCBmaWcud2lkdGg9MTV9DQpncmlkLmFycmFuZ2Uob2xkX21hcCwgbmV3X21hcCwgbmNvbD0yKQ0KYGBgDQoNCiMgSW50ZXJhY3RpdmUgUGxvdA0KDQpGb3IgdGhlIEludGVyYWN0aXZlIHBsb3QgcG9ydGlvbiBvZiB0aGlzIE1pbmktUHJvamVjdCBJIGRlY2lkZWQgdG8gbG9vayBpbnRvIHRoZSBBc3NldCBUcmVuZHMgb2YgdGhlIHRvcCAxMCBDcmVkaXQgVW5pb25zIGluIEZsb3JpZGEuIEkga25vdyBmcm9tIHBhc3QgcHJvamVjdHMgdGhhdCB0aGUgdG9wIDEwIENyZWRpdCBVbmlvbnMgYXJlIEFkZGl0aW9uIEZpbmFuY2lhbCwgQ2FtcHVzIFVTQSwgRWdsaW4sIEZhaXJ3aW5kcywgR3JvdyBGaW5hbmNpYWwsIEdURSwgTWlkZmxvcmlkYSwgU3BhY2UgQ29hc3QsIFN1bmNvYXN0LCBhbmQgVnlzdGFyLg0KDQpgYGB7c3FsLCBjb25uZWN0aW9uPWNvbiwgb3V0cHV0LnZhciA9ICJ0b3BfdGVuX2Fzc2V0cyJ9DQpzZWxlY3QgaWN1LkNVX05BTUUgYXMgJ0NyZWRpdFVuaW9uJw0KLCBjYXN0KGZzLkFDQ1RfMDEwIGFzIG1vbmV5KSBhcyAnQXNzZXRzJw0KLCAoZnMuQUNDVF8wMTAtc3QuQUNDVF8wMTArMC4wKS9zdC5BQ0NUXzAxMCBhcyAnQXNzZXRHcm93dGgnDQosIGNhc3QoZnMuQ1lDTEVfREFURSBhcyBkYXRldGltZSkgYXMgJ0N5Y2xlX0RhdGUnDQpmcm9tIHRibF9Gb2ljdSBpY3UNCmlubmVyIGpvaW4gdGJsX0ZzMjIwIGZzIG9uIChpY3UuQ1VfTlVNQkVSID0gZnMuQ1VfTlVNQkVSIGFuZCBpY3UuQ1lDTEVfREFURSA9IGZzLkNZQ0xFX0RBVEUpDQpsZWZ0IG91dGVyIGpvaW4gdGJsX0ZzMjIwIHN0IG9uIChpY3UuQ1VfTlVNQkVSID0gc3QuQ1VfTlVNQkVSIGFuZCBzdC5DWUNMRV9EQVRFPScyMDE5LTEyLTMxJykNCndoZXJlIDE9MQ0KYW5kIGljdS5TVEFURT0nRkwnDQphbmQgaWN1LmN1X251bWJlciBpbiAoNjg2NDUsNjg0OTAsNjcyOTcsNjg2MDAsNjg0MTcsOTk3NiwxOTYsOTc4OCw2ODM5MSw2ODcwMikNCm9yZGVyIGJ5IGljdS5DVV9OQU1FIGFzYywgZnMuQ1lDTEVfREFURSBkZXNjDQpgYGANCg0KYGBge3J9DQp0b3BfdGVuX2Fzc2V0cyRDeWNsZV9EYXRlIDwtIGFzLkRhdGUodG9wX3Rlbl9hc3NldHMkQ3ljbGVfRGF0ZSkNCmBgYA0KDQpgYGB7cn0NCmFzc2V0X3RyZW5kIDwtIGdncGxvdChkYXRhID0gdG9wX3Rlbl9hc3NldHMpKw0KICBnZW9tX2xpbmUoYWVzKHggPSBDeWNsZV9EYXRlLCB5ID0gQXNzZXRzLzEwMDAwMDAwMDAsIGNvbG9yPUNyZWRpdFVuaW9uKSwgc2l6ZT0xKSsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gQ3ljbGVfRGF0ZSwgeSA9IEFzc2V0cy8xMDAwMDAwMDAwLCBjb2xvcj1DcmVkaXRVbmlvbiwgdGV4dD1wYXN0ZSgiQ3JlZGl0IFVuaW9uOiAiLENyZWRpdFVuaW9uLCI8YnI+RGF0ZToiLEN5Y2xlX0RhdGUsIjxicj5Bc3NldHM6ICQiLCBmb3JtYXQoQXNzZXRzLGJpZy5tYXJrPSIsIikpKSwgc2l6ZT0xKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmRvbGxhcl9mb3JtYXQoKSxicmVha3MgPSByb3VuZChzZXEoMCwgMTUsIGJ5ID0gMSksMSkpKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMiBtb250aCIsIGRhdGVfbGFiZWxzID0gICIlYiAleSIpKw0KICBsYWJzKHk9IkFzc2V0cyAoJCBCaWxsaW9ucykiLHg9IkN5Y2xlIERhdGUiLGNhcHRpb249IiAiLCBjb2xvcj0iQ3JlZGl0IFVuaW9uIix0aXRsZT0iICIpKw0KICB0aGVtZShwbG90LnRpdGxlLnBvc2l0aW9uID0gInBsb3QiKQ0KYGBgDQoNCmBgYHtyfQ0KYXNzZXRfcGxvdGx5IDwtIGdncGxvdGx5KGFzc2V0X3RyZW5kLCB0b29sdGlwID0gInRleHQiLCB3aWR0aCA9IDEwMDApICU+JQ0KICBsYXlvdXQodGl0bGUgPSBsaXN0KHRleHQgPSBwYXN0ZTAoJ0Fzc2V0IFRyZW5kJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8c3VwPicsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnVG9wIDEwIEZsb3JpZGEgQ3JlZGl0IFVuaW9ucycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPC9zdXA+JykpKSAgDQphc3NldF9wbG90bHkNCmBgYA0KDQpgYGB7cn0NCmdyb3d0aF90cmVuZCA8LSBnZ3Bsb3QoZGF0YSA9IHRvcF90ZW5fYXNzZXRzKSsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gQ3ljbGVfRGF0ZSwgeSA9IEFzc2V0R3Jvd3RoLCBjb2xvcj1DcmVkaXRVbmlvbiksIHNpemU9MSkrDQogIGdlb21fbGluZShhZXMoeCA9IEN5Y2xlX0RhdGUsIHkgPSBBc3NldEdyb3d0aCwgY29sb3I9Q3JlZGl0VW5pb24sIHRleHQ9cGFzdGUoIkNyZWRpdCBVbmlvbjogIixDcmVkaXRVbmlvbiwiPGJyPkRhdGU6IixDeWNsZV9EYXRlLCI8YnI+QXNzZXRzOiAkIiwgZm9ybWF0KEFzc2V0cyxiaWcubWFyaz0iLCIpLCI8YnI+R3Jvd3RoOiAiLHJvdW5kKEFzc2V0R3Jvd3RoKjEwMCwxKSwiJSIpKSwgc2l6ZT0xKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCkpKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMiBtb250aCIsIGRhdGVfbGFiZWxzID0gICIlYiAleSIpKw0KICBsYWJzKHk9IkFzc2V0cyBHcm93dGggKCUpIix4PSJDeWNsZSBEYXRlIiwgY29sb3I9IkNyZWRpdCBVbmlvbiIsdGl0bGU9IiAiKQ0KICAjdGhlbWUocGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IikNCmBgYA0KDQpgYGB7cn0NCmdyb3d0aF9wbG90bHkgPC0gZ2dwbG90bHkoZ3Jvd3RoX3RyZW5kLCB0b29sdGlwID0gInRleHQiLCB3aWR0aCA9IDEwMDApICU+JQ0KICBsYXlvdXQodGl0bGUgPSBsaXN0KHRleHQgPSBwYXN0ZTAoJ0Fzc2V0IEdyb3d0aCBTaW5jZSAyMDE5JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8c3VwPicsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnVG9wIDEwIEZsb3JpZGEgQ3JlZGl0IFVuaW9ucycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPC9zdXA+JykpKQ0KZ3Jvd3RoX3Bsb3RseQ0KYGBgDQoNCg==